import { IHeapNode, IHeapSnapshot, utils } from "@memlab/core"
import { ILoggable } from "../utils/Log";
import { ArkAnalyzer } from "./ArkAnalyzer";

/**
 * Ark Heapsnapshot 比较分析器
 */
export class ArkCompareAnalyzer extends ArkAnalyzer implements ILoggable {
    public readonly DOMAIN: string = 'meminsight';
    public readonly TAG = ArkCompareAnalyzer.name;
    public static readonly NAME = "ark-compare-analyzer";
    public static readonly DESC = "Compare two heapsnapshot files and find the differences.";
    public srcSnapshot: IHeapSnapshot | undefined = undefined;
    public dstSnapshot: IHeapSnapshot | undefined = undefined;
    public srcSnapshotFilePath: string | undefined = undefined;
    public dstSnapshotFilePath: string | undefined = undefined;
    public nodeName: string | undefined = undefined;

    constructor() {
        super();
        this.type = 'ark-compare-analyzer';
    }

    public override async analyze(args: any[]) : Promise<any> {
        this.srcSnapshotFilePath = args[0] as string;
        this.dstSnapshotFilePath = args[1] as string;

        this.srcSnapshot = await utils.getSnapshotFromFile(this.srcSnapshotFilePath, { buildNodeIdIndex: true });
        this.dstSnapshot = await utils.getSnapshotFromFile(this.dstSnapshotFilePath, { buildNodeIdIndex: true });
        
        if (!this.srcSnapshot || !this.dstSnapshot) {
            throw new Error("Failed to load heap snapshots");
        }

        return true;
    }

    private getStatsOfAllNodes(snapshot: IHeapSnapshot): Record<string, { count: number; selfSize: number; retainedSize: number }> {
        const stats: Record<string, { count: number; selfSize: number; retainedSize: number }> = {};
        snapshot.nodes.forEach((node: IHeapNode) => {
            const typeName = node.name;
            if (!stats[typeName]) {
                stats[typeName] = { count: 0, selfSize: 0, retainedSize: 0 };
            }
            
            stats[typeName].count += 1;
            stats[typeName].selfSize += node.self_size;
            stats[typeName].retainedSize += node.retainedSize;
        });
        
        return stats;
    }

    private getStatsByNodeName(snapshot: IHeapSnapshot, name: string) : Record<string, { count: number; selfSize: number; retainedSize: number }> {
        const stats: Record<string, { count: number; selfSize: number; retainedSize: number }> = {};
        snapshot.nodes.forEach((node: IHeapNode, _) => {
            if (node.name === name) {
                if (!stats[name]) {
                    stats[name] = { count: 0, selfSize: 0, retainedSize: 0 };
                }
                stats[name].count += 1;
                stats[name].selfSize += node.self_size;
                stats[name].retainedSize += node.retainedSize;
            }
        });
        return stats;
    }

    public getDetailStatsByNodeName(snapshot: IHeapSnapshot, nodeName: string): Array<{ id: number; selfSize: number; retainedSize: number }> {
        const stats = new Array<{ id: number; selfSize: number; retainedSize: number }>();
        snapshot.nodes.forEach((node: IHeapNode, _) => {
            if (node.name === nodeName) {
                stats.push({ id: node.id, selfSize: node.self_size, retainedSize: node.retainedSize })
            }
        });
        return stats;
    }

    private getDiffs(record1: Record<string, { count: number; selfSize: number; retainedSize: number }>,
         record2: Record<string, { count: number; selfSize: number; retainedSize: number }>): Array<{
        typeName: string;
        srcCount: number;
        dstCount: number;
        countDiff: number;
        srcSelfSize: number;
        dstSelfSize: number;
        selfSizeDiff: number;
        srcRetainedSize: number;
        dstRetainedSize: number;
        retainedSizeDiff: number;
    }> {
        const allTypes = new Set([
            ...Object.keys(record1),
            ...Object.keys(record2)
        ]);
        const diffs = [];
        for (const typeName of allTypes) {
            const srcStats = record1[typeName] || { count: 0, selfSize: 0, retainedSize: 0 };
            const dstStats = record2[typeName] || { count: 0, selfSize: 0, retainedSize: 0 }; 
            if (srcStats.count !== dstStats.count || srcStats.selfSize !== dstStats.selfSize || srcStats.retainedSize !== dstStats.retainedSize) {
                diffs.push({
                    typeName,
                    srcCount: srcStats.count,
                    dstCount: dstStats.count,
                    countDiff: dstStats.count - srcStats.count,
                    srcSelfSize: srcStats.selfSize,
                    dstSelfSize: dstStats.selfSize,
                    selfSizeDiff: dstStats.selfSize - srcStats.selfSize,
                    srcRetainedSize: srcStats.retainedSize,
                    dstRetainedSize: dstStats.retainedSize,
                    retainedSizeDiff: dstStats.retainedSize - srcStats.retainedSize
                });
            }
        }
        return diffs;
    }

    public diffSnapshots(node_name?: string): Array<{
        typeName: string;
        srcCount: number;
        dstCount: number;
        countDiff: number;
        srcSelfSize: number;
        dstSelfSize: number;
        selfSizeDiff: number;
        srcRetainedSize: number;
        dstRetainedSize: number;
        retainedSizeDiff: number;
    }> {
        let srcTypeStats = undefined;
        let dstTypeStats = undefined;
        if (node_name) {
            // 按 node_name 统计源快照中的对象类型
            srcTypeStats = this.getStatsByNodeName(this.srcSnapshot!, node_name);
            // 按 node_name 统计目标快照中的对象类型
            dstTypeStats = this.getStatsByNodeName(this.dstSnapshot!, node_name);
        } else {
            // 统计源快照中的对象类型
            srcTypeStats = this.getStatsOfAllNodes(this.srcSnapshot!);
            // 统计目标快照中的对象类型
            dstTypeStats = this.getStatsOfAllNodes(this.dstSnapshot!);
            // console.log(`srcTypeStats: ${JSON.stringify(srcTypeStats)}`);
            // console.log(`dstTypeStats: ${JSON.stringify(dstTypeStats)}`);
        }
        return this.getDiffs(srcTypeStats, dstTypeStats);
    }

    public async sortDiffs(type: number, 
        diffs: Array<{
        typeName: string;
        srcCount: number;
        dstCount: number;
        countDiff: number;
        srcSelfSize: number;
        dstSelfSize: number;
        selfSizeDiff: number;
        srcRetainedSize: number;
        dstRetainedSize: number;
        retainedSizeDiff: number
    }>): Promise<any> {
        let result: Array<any> | undefined = undefined;
        if (type === 0) {
            result = [...diffs].sort((a, b) => Math.abs(b.countDiff) - Math.abs(a.countDiff));
        } else if (type === 1) {
            result = [...diffs].sort((a, b) => Math.abs(b.selfSizeDiff) - Math.abs(a.selfSizeDiff));
        } else if (type === 2) {
            result = [...diffs].sort((a, b) => Math.abs(b.retainedSizeDiff) - Math.abs(a.retainedSizeDiff));
        } else {
            result = [...diffs].sort((a, b) => Math.abs(b.countDiff) - Math.abs(a.countDiff));
        }
        return result;
    }
}